/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.altuure.yagdao.hibernate;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;

import com.altuure.yagdao.GenericDAO;
import com.altuure.yagdao.SearchResultList;
import com.altuure.yagdao.YFetch;
import com.altuure.yagdao.YLimit;
import com.altuure.yagdao.YOrder;
import com.altuure.yagdao.YPage;
import com.altuure.yagdao.common.Utils;

/**
 * Generic DAO for object DAO.
 * @author altuure
 */
public class GenericHibernateDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    /**
     * Handled entity class
     */
    private Class<T> objectClass;
    /**
     * utility to access session
     */
    private HibernateSessionAccessor sessionAccessor;

    /**
     * id field
     */
    private String idProperty;

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#getObjectClass()
     */
    public Class getObjectClass() {
        return objectClass;
    }

    /**
     * set object class manual
     * @param objectClass
     */
    public void setObjectClass(Class objectClass) {
        this.objectClass = objectClass;
    }

    /**
     * get session accessor
     * @return value
     */
    public HibernateSessionAccessor getSessionAccessor() {
        return sessionAccessor;
    }

    /**
     * set session accessor
     * @param sessionAccessor set value
     */
    public void setSessionAccessor(HibernateSessionAccessor sessionAccessor) {
        this.sessionAccessor = sessionAccessor;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#save(java.lang.Object)
     */
    public void save(Object object) throws HibernateException {

        getSession().save(object);

    }

    /**
     * util method for accesing session
     * @return
     */
    public Session getSession() {
        return sessionAccessor.getCurrentSession();
    }

    /** LAZY loading **/
    public T load(ID id) throws HibernateException {
        return (T) getSession().load(getObjectClass(), id);
    }

    /**
     * fetch with requested fetchs
     **/
    public T fetch(ID id) throws HibernateException {

        YFetch daoFetch;
        Criteria criteria = getSession().createCriteria(objectClass);
        daoFetch = (YFetch) objectClass.getAnnotation(YFetch.class);
        setFetchModes(criteria, daoFetch);

        return (T) criteria.add(Restrictions.eq(getIdProperty(), id)).uniqueResult();

    }

    /**
     * @param criteria
     * @param daoFetch
     */
    protected void setFetchModes(Criteria criteria, YFetch daoFetch) {
        if (daoFetch != null) {
            String[] strings = daoFetch.value();
            for (String s : strings) {
                criteria.setFetchMode(s, FetchMode.JOIN);
            }

        }
    }

    /**
     * select and load
     * @see #load(java.io.Serializable)
     **/
    public T get(ID id) throws HibernateException {

        return (T) getSession().get(getObjectClass(), id);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#count()
     */
    public long count() {
        Criteria criteria = createCriteria();
        return count(criteria);
    }

    /**
     * cout the criteria query
     * @param criteria
     * @return
     */
    public long count(Criteria criteria) {
        Number result = (Number) criteria.setProjection(Projections.rowCount()).uniqueResult();
        return result.longValue();

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#update(java.lang.Object)
     */
    public void update(Object object) throws HibernateException {

        getSession().update(object);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#deleteById(java.io.Serializable)
     */
    public void deleteById(ID id) throws HibernateException {
        delete(load(id));

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#delete(java.lang.Object)
     */
    public void delete(Object object) throws HibernateException {
        if (object == null)
            return;

        getSession().delete(object);
    }

    /**
     * vict object from session
     * @param object
     * @throws HibernateException
     */
    public void evict(Object object) throws HibernateException {
        getSession().evict(object);
    }

    /**
     * create criteria query
     * @return
     */
    protected Criteria createCriteria() {
        Class persistentClass = getObjectClass();
        return createCriteria(persistentClass);
    }

    /**
     * create criteria query for given class
     * @param persistentClass
     * @return
     */
    protected Criteria createCriteria(Class persistentClass) {
        return getSession().createCriteria(persistentClass);
    }

    protected SearchResultList<T> query(Criteria criteria) {
        return query(criteria, null);
    }

    /**
     * query with given sort and order
     * @param query query
     * @param paging sort and order
     * @return result set
     */
    protected SearchResultList query(Query query, YLimit paging) {
        if (paging == null)
            return new SearchResultList<T>(query.list());

        if (paging.isPaged())
            query.setFirstResult(paging.getFirstResult());
        if (paging.isPageSized())
            query.setMaxResults(paging.getPageSize());

        return new SearchResultList<T>(query.list());

    }

    /**
     * execute criteria wuth given criteria and sort and order
     * @param criteriaPass criteria
     * @param paging sort and order
     * @return result set
     */
    protected SearchResultList<T> query(Criteria criteriaPass, YPage paging) {
        CriteriaImpl criteria = (CriteriaImpl) criteriaPass;

        long count = count(criteria);

        criteria.setProjection(null);
        criteria.setResultTransformer(Criteria.ROOT_ENTITY);

        setOrders(criteria, paging);

        setLimits(criteria, paging);

        return new SearchResultList<T>(criteria.list(), count, paging);
    }

    /**
     * setOrders
     * @param criteria
     * @param paging
     */
    public void setOrders(CriteriaImpl criteria, YPage paging) {
        if (paging == null)
            return;
        List<YOrder> orders = paging.getOrders();
        if (orders != null) {
            for (YOrder daoOrder : orders) {
                String name = daoOrder.getName();
                if (!Utils.isEmpty(name) && name.indexOf(".") < 0) {
                    criteria.addOrder((daoOrder.getSort().equals(YOrder.Sort.ASC)) ? Order.asc(name) : Order.desc(name));
                } else {
                    String[] splitDot = Utils.splitDot(name);
                    Criteria findCriteria = findCriteria(criteria, splitDot);
                    findCriteria.addOrder((daoOrder.getSort().equals(YOrder.Sort.ASC)) ? Order
                            .asc(splitDot[splitDot.length - 1]) : Order.desc(splitDot[splitDot.length - 1]));
                }

            }
        }
    }

    /**
     * set limits for query.
     * @param criteria criteria
     * @param limits limits
     */
    void setLimits(Criteria criteria, YLimit limits) {
        if (limits == null)
            return;
        if (limits.getFirstResult() >= 0)
            criteria.setFirstResult(limits.getFirstResult());
        if (limits.getPageSize() >= 0)
            criteria.setMaxResults(limits.getPageSize());
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#loadAll()
     */
    public SearchResultList<T> getAll() {
        return getAll(null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#getAll(com.altuure.yagdao.DAOPage)
     */
    public SearchResultList<T> getAll(YPage paging) {
        return query(createCriteria(), paging);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#fetchAll()
     */
    public SearchResultList<T> fetchAll() {
        return fetchAll(null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.altuure.yagdao.GenericDAO#fetchAll(com.altuure.yagdao.DAOPage)
     */
    public SearchResultList<T> fetchAll(YPage page) {
        YFetch daoFetch;
        Criteria criteria = getSession().createCriteria(objectClass);
        daoFetch = (YFetch) objectClass.getAnnotation(YFetch.class);
        setFetchModes(criteria, daoFetch);
        return query(criteria, page);

    }

    /**
     * returns the id property for given entity
     * @return id proeprty name
     */
    public String getIdProperty() {
        if (idProperty == null)
            idProperty = sessionAccessor.getSessionFactory().getClassMetadata(objectClass).getIdentifierPropertyName();
        return idProperty;
    }

    /**
     * find the related criteria from root
     * @param rootCriteria root criteria
     * @param strings property array
     * @return regarding criteria
     */
    protected Criteria findCriteria(CriteriaImpl rootCriteria, String[] strings) {
        Criteria root = rootCriteria;
        int index = 0;
        while (index < strings.length - 1) {
            Iterator iterator = rootCriteria.iterateSubcriteria();
            boolean f = false;
            while (iterator.hasNext()) {
                CriteriaImpl.Subcriteria next = (CriteriaImpl.Subcriteria) iterator.next();
                if (next.getParent() == root && next.getPath().equals(strings[index])) {
                    root = next;
                    index++;
                    f = true;
                    break;
                }

            }
            if (!f) {
                StringBuffer sb = new StringBuffer();
                for (int i = index; i < strings.length - 1; i++) {
                    if (i > 0)
                        sb.append(".");
                    sb.append(strings[i]);
                    root = root.createCriteria(strings[i]);

                }
                return root;
            }

        }
        return root;

    }

    public void detach(Object entity) {
        getSession().evict(entity);

    }

}
